package org.excilys.training.ebanking.service;

import java.util.List;

import org.excilys.training.ebanking.idao.IDaoCompte;
import org.excilys.training.ebanking.idao.IDaoOperation;
import org.excilys.training.ebanking.idao.IDaoTypeOperation;
import org.excilys.training.ebanking.iservice.IServiceCompte;
import org.excilys.training.ebanking.model.Compte;
import org.excilys.training.ebanking.model.Operation;
import org.excilys.training.ebanking.model.TypeOperation;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.googlecode.ehcache.annotations.Cacheable;

@Service
@Transactional(readOnly = true)
public class ServiceCompte implements IServiceCompte {

	private IDaoCompte iDaoCompte;
	static final Logger LOGGER = LoggerFactory.getLogger(ServiceCompte.class);
	static int i = 0;

	@Autowired
	private IDaoTypeOperation iDaoTypeOperation;

	@Autowired
	private IDaoOperation iDaoOperation;

	@Autowired
	public void setiDaoCompte(IDaoCompte iDaoCompte) {
		this.iDaoCompte = iDaoCompte;
	}

	/**
	 * Méthode permettant au cliant de sélectionner un compte donné
	 * <p>
	 *
	 * @param id
	 *            numéro d'un compte
	 *
	 * @return solde du compte
	 *
	 */
	public double getSolde(int id) {

		LOGGER.info("chargement du solde du compte (id=)n°'{}'", id);
		return iDaoCompte.getAccount(id).getSoldeCourant();
	}

	/**
	 * Méthode recupérant la liste de comptes d'un client
	 * <p>
	 *
	 * @param id
	 *            identifiant d'un client
	 *
	 * @return liste des comptes d'un client
	 */
	public List<Compte> getAllAccountById(int id) {
		List<Compte> comptes = iDaoCompte.getAllAccountByClientId(id);

		if (comptes.isEmpty()) {

			LOGGER.warn("La listes des comptes inexistent pour l'utilisateur dont  id='{}' ",id);
			return comptes;
		}

		LOGGER.info("Chargement des comptes de l'utilisateur (id=) n°'{}'", id);
		return comptes;
	}

	/**
	 * Méthode retournant un compte a l'aide de son identifiant
	 * <p>
	 *
	 * @param id
	 *            numero de compte
	 *
	 * @return compte
	 */
	@Cacheable(cacheName = "idCompteCache")
	// FIXME flush cache entry on update
	public Compte getAccount(int id) {
		Compte compte = iDaoCompte.getAccount(id);
		LOGGER.info("Chargement d'un compte (id=)n°'{}'", id);
		if (compte == null) {
			LOGGER.error("Compte inexistent pour id='{}'", id);
		}

		return compte;
	}

	/**
	 * méthode permettant de calculer l'encours d'un compte donné
	 * <p>
	 *
	 * @param id
	 *            numéro de compte
	 *
	 * @return encours du compte
	 */
	public double getEncours(int id) {

		LOGGER.info("Chargement de l'encours du compte (id=) n°'{}'", id);
		return iDaoCompte.getEncours(id);
	}

	/**
	 * Méthode permettant de calculer le solde prévisonnel d'un compte donné
	 * <p>
	 *
	 * @param id
	 *            numéro de compte
	 *
	 * @return solde prévisionnel du compte
	 */
	public double getSoldePrevisonnel(int id) {
		LOGGER.info("Calcul du solde prévisionnelle du compte (id) n°='{}'", id);
		return round(getEncours(id) + getSolde(id), 2);
	}

	/**
	 * Méthode permettant d'arrondir un double
	 *
	 * @param Rval
	 *            valeur à arrondir
	 * @param Rpl
	 *            nombre de chiffre après la virgule
	 * @return
	 */
	public static double round(double Rval, int Rpl) {
		double p = (double) Math.pow(10, Rpl);
		Rval = Rval * p;
		double tmp = Math.round(Rval);
		return (double) tmp / p;
	}

	/**
	 * Methode permettant de réalisé un débit compte Comprend la mise à jour du
	 * compte et la céation de l'opération
	 *
	 * @param idCompteSource
	 * @param idCompteCible
	 * @param libelle
	 * @param montant
	 * @param idTypeOperation
	 *
	 */
	public void debitCompte(int idCompteSource, int idCompteCible,
			String libelle, double montant, int idTypeOperation) {
		// Mise a jour du solde du compte debité
		Compte compte = iDaoCompte.getAccount(idCompteSource);
		montant = -montant;
		double nouveauSolde = compte.getSoldeCourant() + montant;
		compte.setSoldeCourant(nouveauSolde);
		// Mise a jour du compte en base
		iDaoCompte.updateAccount(compte);
		// Creation de la nouvelle operation et sauvegarde en base
		TypeOperation typeOperation = iDaoTypeOperation
				.getTypeOperation(idTypeOperation);
		DateTime dateCourante = new DateTime();
		Operation operation = new Operation.Builder()
									.withCompte(compte)
									.withCompteDestinataire(idCompteCible)
									.withLibelle(libelle)
									.withMontant(montant)
									.withtypeOperation(typeOperation)
									.withDateOperation(dateCourante)
									.build();;
		iDaoOperation.createOperation(operation);
	}

	/**
	 * Methode permettant de réalisé un crédit compte Comprend la mise à jour du
	 * compte et la céation de l'opération
	 *
	 * @param idCompteSource
	 * @param idCompteCible
	 * @param libelle
	 * @param montant
	 * @param idTypeOperation
	 *
	 */
	public void creditCompte(int idCompteSource, int idCompteCible,
			String libelle, double montant, int idTypeOperation) {
		// Mise a jour du solde du compte debité
		Compte compte = iDaoCompte.getAccount(idCompteSource);
		double nouveauSolde = compte.getSoldeCourant() + montant;
		compte.setSoldeCourant(nouveauSolde);
		// Mise a jour du compte en base
		iDaoCompte.updateAccount(compte);
		// Creation de la nouvelle operation et sauvegarde en base
		TypeOperation typeOperation = iDaoTypeOperation
				.getTypeOperation(idTypeOperation);
		DateTime dateCourante = new DateTime();
		Operation operation = new Operation.Builder()
								.withCompte(compte)
								.withCompteDestinataire(idCompteCible)
								.withLibelle(libelle)
								.withMontant(montant)
								.withtypeOperation(typeOperation)
								.withDateOperation(dateCourante)
								.build();
		iDaoOperation.createOperation(operation);
	}

	/**
	 * Méthode permettant de réaliser un Virement Interne à la banque Comprend
	 * le débit et le crédit
	 *
	 * @param idCompteSource
	 * @param idCompteCible
	 * @param montant
	 * @param idTypeOperation
	 *
	 */
	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
	public void transfertInterne(int idCompteSource, int idCompteCible,
			double montant, int idTypeOperation) {
		String libelle = "Virement du compte n°" + idCompteSource + "vers n°"
				+ idCompteCible;
		debitCompte(idCompteSource, idCompteCible, libelle, montant,
				idTypeOperation);
		creditCompte(idCompteCible, idCompteCible, libelle, montant,
				idTypeOperation);
	}

	/**
	 * Méthode permettant de réaliser un Virement Externe à la banque Comprend
	 * le débit
	 *
	 * @param idCompteSource
	 * @param idCompteCible
	 * @param montant
	 * @param idTypeOperation
	 *
	 */
	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
	public void transfertExterne(int idCompteSource, int idCompteCible,
			double montant, int idTypeOperation) {
		String libelle = "Virement du compte n°" + idCompteSource + "vers n°"
				+ idCompteCible;
		debitCompte(idCompteSource, idCompteCible, libelle, montant,
				idTypeOperation);
	}

	public boolean testSoldeCompte(int idCompte, double montant) {
		if (iDaoCompte.getAccount(idCompte).getSoldeCourant() - montant > 0) {
			return true;
		}
		return false;
	}
}
